/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Session.h"
#include <cassert>
#include <cstdarg>
#include "UGame/System/LockGuard.h"
#include "UGame/Util/String.h"
#include "SqlConnection.h"
#include "SqlConnectionFactory.h"
#include "SqlDelayThread.h"
#include "SqlOperation.h"
using UGame::System::LockGuard;
using UGame::System::Mutex;

namespace UGame {
namespace Data {
namespace Rel {

    Session::Session(void)
        : _allowAsyncTrans(true)
        , _syncConns()
        , _syncConnUseIndex(-1)
		, _syncConnsLock()
        , _asyncConn()
        , _stmtRegistry()
        , _stmtRegistryLock()
        , _stmtIndex(0)
        , _resultQueue(new SqlResultQueue)
        , _delayThread()
        , _errHandler(NULL)
    {

    }

    Session::~Session(void)
    {
        _Uninitialize();
    }

    bool Session::Open(const std::string& _Key, int _QueryConns, 
        const std::string& _Host, const std::string& _Port, 
        const std::string& _User, const std::string _Password, 
        const std::string& _Db)
    {
        /* Must set error handler first */
        UASSERT(_errHandler != NULL);

        /* Create connections */
        _QueryConns = UMAX(_QueryConns, 1);
        for (int _Nconn = 0; _Nconn < _QueryConns; ++_Nconn)
        {
            SqlConnection* _Conn = SqlConnectionFactory::Create(this, _Key, _Host, _Port, _User, _Password, _Db);
            if (!_Conn) {
                return false;
            }
            _syncConns.push_back(_Conn);
        }

        _asyncConn.reset(SqlConnectionFactory::Create(this, _Key, _Host, _Port, _User, _Password, _Db));
		if (!_asyncConn.get()) {
            return false;
        }

        /* Create delay thread */
        _delayThread.reset(new SqlDelayThread(this, _asyncConn.get()));
		if (!(_delayThread.get() && _delayThread->Start())) {
            return false;
        }

        return true;
    }

    void Session::Close(void)
    {
        /* Close delay thead first */
        if (_delayThread.get()) {
            _delayThread->Stop();
            _delayThread.reset();
        }

        /* Close connections */
        if (_asyncConn.get()) {
			_asyncConn->Close();
			_asyncConn.reset();
        }
        while (!_syncConns.empty()) {
			std::auto_ptr<SqlConnection> _Conn(_syncConns.back());
			_syncConns.pop_back();

            _Conn->Close();
        }
    }

    void Session::EscapeString(std::string& _Sql)
    {
        _syncConns[0]->EscapeString(_Sql);
    }

    void Session::Ping(void)
    {
        std::vector<SqlConnection*>::iterator _Iter = _syncConns.begin();
        for (; _Iter != _syncConns.end(); ++_Iter) {
            (*_Iter)->Ping();
        }
    }

    std::string Session::GetServerInfo(void)
    {
        return _syncConns[0]->GetServerInfo();
    }

    std::string Session::GetClientInfo(void)
    {
		return _syncConns[0]->GetClientInfo();
    }

    void Session::SetErrorHandler(ERROR_HANDLER _Handler)
    {
        UASSERT(_errHandler == NULL && _Handler != NULL);
        _errHandler = _Handler;
    }

    QueryResult* Session::PQuery(const char* _Sql, ...)
    {
        char _Lsql[MAX_QUERY_LENGTH];

        std::va_list _Arg;
        va_start(_Arg, _Sql);
        int _Ret = Util::vsnprintf(_Lsql, MAX_QUERY_LENGTH, _Sql, _Arg);
        va_end(_Arg);

        if (_Ret == -1) {
            _errHandler(-1, "SQL Query truncated (and not execute) for format: %s\n", _Sql);
            return NULL;
        }
		return _GetConnection()->Query(_Lsql);
    }

    bool Session::PExecute(const char* _Sql, ...)
    {
        char _Lsql[MAX_QUERY_LENGTH];

        std::va_list _Arg;
        va_start(_Arg, _Sql);
        int _Ret = Util::vsnprintf(_Lsql, MAX_QUERY_LENGTH, _Sql, _Arg);
        va_end(_Arg);

        if (_Ret == -1)
        {
            _errHandler(-1, "SQL Query truncated (and not execute) for format: %s\n", _Sql);
            return false;
        }
		return _GetConnection()->Execute(_Lsql);
    }

    SqlStatement Session::CreateStatement(SqlStatementID& _ID, const char* _Format)
    {
        int _Id = -1;
        if (!_ID.IsInitialized())
        {
            std::string _SzFmt(_Format);
            std::size_t _Params = std::count(_SzFmt.begin(), _SzFmt.end(), '?');
            UMUTEX_LOCK_ASSERT(_Guard, _stmtRegistryLock);
            PreparedStmtRegistry::const_iterator _Iter = _stmtRegistry.find(_SzFmt);
            if (_Iter == _stmtRegistry.end())
            {
                _Id = ++_stmtIndex;
                _stmtRegistry[_SzFmt] = _Id;
            }
            else {
                _Id = _Iter->second;
            }

            _ID._Init(_Id, _Params);
        }
        return SqlStatement(_ID, *this);
    }

    bool Session::ExecuteStatement(const SqlStatementID& _ID, SqlStmtParameters* _Params)
    {
        if (!_allowAsyncTrans) {
            return DirectExecuteStatement(_ID, _Params);
        }
        _delayThread->Delay(new SqlPreparedRequest(_ID.id(), _Params));
        return true;
    }

    bool Session::DirectExecuteStatement(const SqlStatementID& _ID, SqlStmtParameters* _Params)
    {
        UASSERT(_Params);
        std::auto_ptr<SqlStmtParameters> _P(_Params);
        SqlConnection::Lock _Guard(_asyncConn.get());
        return _Guard->ExecuteStmt(_ID.id(), *_Params);
    }

    std::string Session::GetStatementString(const int _ID)
    {
        UMUTEX_LOCK_ASSERT(_Guard, _stmtRegistryLock);

        if (_ID == -1 || _ID > _stmtIndex) {
            return std::string();
        }

        PreparedStmtRegistry::const_iterator _Iter = _stmtRegistry.begin();
        PreparedStmtRegistry::const_iterator _IterLast = _stmtRegistry.end();
        for (; _Iter != _IterLast; ++_Iter)
        {
            if (_Iter->second == _ID)
            {
                return _Iter->first;
            }
        }
        return std::string();
    }

    void Session::ProcessAsyncResultQueue(void)
    {
        if (_resultQueue.get()) {
            _resultQueue->Update();
        }
    }

    SqlConnection* Session::_GetConnection(void)
    {
		UMUTEX_LOCK_ASSERT(_Guard, _syncConnsLock);
        if (++_syncConnUseIndex >= (int)_syncConns.size()) {
            _syncConnUseIndex = 0;
        }
        return _syncConns[_syncConnUseIndex];
    }

    void Session::_Uninitialize(void)
    {
        Close();

        /* Clear async result queue */
        if (_resultQueue.get())
        {
            IQueryCallback* _Callback = NULL;
            while (_resultQueue->Next(_Callback)) {
                delete _Callback;
            }
        }
    }

}}}